﻿using BoardGame;
using System;
using System.Collections;
using System.Collections.Generic;


namespace GameModelView
{
    public class PieceLists
    {
        private List<Piece>[] items;

        internal PieceLists () { Clear (); }

        public List<Piece> this[Player player]
        { get { return items[(int) player]; } }

        public int Count (Player player)
        { return items[(int) player].Count; }

        internal void Add (Piece newPiece)
        { items[(int) newPiece.Side].Add (newPiece); }


        internal void Clear ()
        {
            if (items == null || items.Length != 2)
                items = new List<Piece>[] { new List<Piece> (), new List<Piece> () };
            else
            {
                items[0].Clear ();
                items[1].Clear ();
            }
        }


        // Used by pawn promotion.
        internal void Replace (Piece oldPiece, Piece newPiece)
        {
            for (int i = 0; i < Count (oldPiece.Side); ++i)
                if (this[oldPiece.Side][i].At.X == oldPiece.At.X && this[oldPiece.Side][i].At.Y == oldPiece.At.Y)
                {
                    this[oldPiece.Side][i] = newPiece;
                    break;
                }
        }
    }


    public abstract class Piece : Square
    {
        public ChessModelView Game { get; internal set; }
        public Player Side { get; protected set; }
        public Coordinate At { get; internal set; }
        public bool IsCaptured { get; internal set; }
        internal int firstMoved = -1;

        protected Piece (Player side)
        { 
            this.Game = null;
            this.Side = side;
            this.At = new Coordinate (-1, -1);
        }

        protected Piece (Piece piece)
        {
            this.Game = piece.Game;
            this.Side = piece.Side;
            this.At = piece.At;
        }


        public Piece MakeOpposite ()
        { return Factory (ChessModelView.Opponent (this.Side)); }


        public abstract char Letter { get; }
        public abstract Token Token { get; }
        internal abstract Piece Factory (Player newSide);
        internal abstract IEnumerable GetMoves ();

        public bool NeverMoved { get { return firstMoved < 0; } }

        public override bool IsOpponent (Player player) { return this.Side != player; }
        public override bool NotPlayer (Player player) { return this.Side != player; }
        public override bool IsPlayer (Player player) { return this.Side == player; }


        public IEnumerable GetLegalMoves ()
        {
            if (Game.IsUnderway)
                if (this.Side == Game.PlayerTurn)
                    foreach (Coordinate moveTo in GetMoves())
                    {
                        Game.Plies.Push (this, moveTo.X, moveTo.Y);
                        bool threatened = Game.IsKingThreatened (Side);
                        Game.Plies.Pop ();

                        if (!threatened)
                            yield return moveTo;
                    }
        }


        public override string ToString ()
        {
            return (Side == Player.Black? Char.ToLower (Letter) : Letter).ToString();
        }
    }



    public class King : Piece
    {
        public King (Player player) : base (player)
        { }

        internal override Piece Factory (Player player)
        { return new King (player); }

        public override char Letter { get { return 'K'; } }
        public override Token Token { get { return Token.King; } }


        internal override IEnumerable GetMoves ()
        {
            int xMin = At.X - 1;
            if (xMin < 0) xMin = 0;
            int xMax = At.X + 1;
            if (xMax >= Game.Board.XSize) xMax = Game.Board.XSize - 1;

            if (At.Y > 0)
                for (int x = xMin; x <= xMax; ++x)
                    if (Game.Board[x, At.Y - 1].NotPlayer (Side))
                        yield return new Coordinate (x, At.Y - 1);

            if (At.Y < Game.Board.YSize - 1)
                for (int x = xMin; x <= xMax; ++x)
                    if (Game.Board[x, At.Y + 1].NotPlayer (Side))
                        yield return new Coordinate (x, At.Y + 1);

            bool testedLeft = false; bool testedRight = false;

            if (NeverMoved && Game.Status == GameStatus.Go)
            {
                Piece rook = Game.Board[0, At.Y] as Rook;
                if (rook != null && rook.Side == Side)
                    if (rook.NeverMoved && Game.Board[1, At.Y] == Square.Empty)
                        if (Game.Board[At.X - 1, At.Y] == Square.Empty && Game.Board[At.X - 2, At.Y] == Square.Empty)
                        {
                            Game.Plies.Push (this, At.X - 1, At.Y);
                            bool threatened = Game.IsKingThreatened (Side);
                            Game.Plies.Pop ();
                            if (!threatened)
                            {
                                testedLeft = true;
                                yield return new Coordinate (At.X - 1, At.Y);
                                yield return new Coordinate (At.X - 2, At.Y);
                            }
                        }

                rook = Game.Board[Game.Board.XSize - 1, At.Y] as Rook;
                if (rook != null && rook.Side == Side)
                    if (rook.NeverMoved)
                        if (Game.Board[At.X + 1, At.Y] == Square.Empty && Game.Board[At.X + 2, At.Y] == Square.Empty)
                        {
                            Game.Plies.Push (this, At.X + 1, At.Y);
                            bool threatened = Game.IsKingThreatened (Side);
                            Game.Plies.Pop ();
                            if (!threatened)
                            {
                                testedRight = true;
                                yield return new Coordinate (At.X + 1, At.Y);
                                yield return new Coordinate (At.X + 2, At.Y);
                            }
                        }
            }

            if (!testedLeft && At.X > 0 && Game.Board[At.X - 1, At.Y].NotPlayer (Side))
                yield return new Coordinate (At.X - 1, At.Y);

            if (!testedRight && At.X < Game.Board.XSize - 1 && Game.Board[At.X + 1, At.Y].NotPlayer (Side))
                yield return new Coordinate (At.X + 1, At.Y);
        }
    }


    public class Queen : Piece
    {
        public Queen (Player player) : base (player)
        { }

        internal override Piece Factory (Player player)
        { return new Queen (player); }

        public override char Letter { get { return 'Q'; } }
        public override Token Token { get { return Token.Queen; } }


        internal override IEnumerable GetMoves ()
        {
            foreach (Coordinate move in new Bishop (this).GetMoves())
                yield return move;

            foreach (Coordinate move in new Rook (this).GetMoves())
                yield return move;
        }
    }


    public class Rook : Piece
    {
        public Rook (Player player) : base (player)
        { }

        internal Rook (Queen queen) : base (queen)
        { }

        internal override Piece Factory (Player player)
        { return new Rook (player); }

        public override char Letter { get { return 'R'; } }
        public override Token Token { get { return Token.Rook; } }


        internal override IEnumerable GetMoves ()
        {
            for (int x = At.X - 1; x >= 0; --x)
            {
                Square target = Game.Board[x, At.Y];
                if (target.NotPlayer (Side))
                    yield return new Coordinate (x, At.Y);
                if (target.IsOccupied)
                    break;
            }

            for (int x = At.X + 1; x < Game.Board.XSize; ++x)
            {
                Square target = Game.Board[x, At.Y];
                if (target.NotPlayer (Side))
                    yield return new Coordinate (x, At.Y);
                if (target.IsOccupied)
                    break;
            }

            for (int y = At.Y - 1; y >= 0; --y)
            {
                Square target = Game.Board[At.X, y];
                if (target.NotPlayer (Side))
                    yield return new Coordinate (At.X, y);
                if (target.IsOccupied)
                    break;
            }

            for (int y = At.Y + 1; y < Game.Board.YSize; ++y)
            {
                Square target = Game.Board[At.X, y];
                if (target.NotPlayer (Side))
                    yield return new Coordinate (At.X, y);
                if (target.IsOccupied)
                    break;
            }
        }
    }


    public class Bishop : Piece
    {
        static private readonly int[][] deltas = new int[][]
        {
            new int[] { -1, -1 }, new int[] { -1, 1 },
            new int[] { 1, -1 }, new int[] { 1, 1 }
        };

        public Bishop (Player player) : base (player)
        { }

        internal Bishop (Queen queen) : base (queen)
        { }

        internal override Piece Factory (Player player)
        { return new Bishop (player); }

        public override char Letter { get { return 'B'; } }
        public override Token Token { get { return Token.Bishop; } }


        internal override IEnumerable GetMoves ()
        {
            foreach (int[] delta in deltas)
            {
                for (int x = At.X, y = At.Y; ; )
                {
                    x += delta[0];
                    y += delta[1];

                    if (x < 0 || y < 0 || x >= Game.Board.XSize || y >= Game.Board.YSize)
                        break;

                    Square target = Game.Board[x, y];
                    if (target.NotPlayer (Side))
                        yield return new Coordinate (x, y);
                    if (target.IsOccupied)
                        break;
                }
            }
        }
    }


    public class Knight : Piece
    {
        static private readonly int[][] deltas = new int[][]
        {
            new int[] { -2, -1 }, new int []{ -2, 1 },
            new int[] { -1, -2 }, new int[] { -1, 2 },
            new int[] { 1, -2 }, new int[] { 1, 2 },
            new int[] { 2, -1 }, new int[] { 2, 1 }
        };

        public Knight (Player player) : base (player)
        { }

        internal override Piece Factory (Player player)
        { return new Knight (player); }

        public override char Letter { get { return 'N'; } }
        public override Token Token { get { return Token.Knight; } }


        internal override IEnumerable GetMoves ()
        {
            foreach (int[] delta in deltas)
            {
                int x = At.X - delta[0];
                int y = At.Y - delta[1];
                if (x >= 0 && y >= 0 && x < Game.Board.XSize && y < Game.Board.YSize)
                    if (Game.Board[x, y].NotPlayer (this.Side))
                        yield return new Coordinate (x, y);
            }
        }
    }


    public class Pawn : Piece
    {
        public Pawn (Player player) : base (player)
        { }

        internal override Piece Factory (Player player)
        { return new Pawn (player); }

        public override char Letter { get { return 'P'; } }
        public override Token Token { get { return Token.Pawn; } }


        private IEnumerable GetCaptures (int x, int yDelta, int passantRank)
        {
            Square target = Game.Board[x, At.Y + yDelta];
            if (target.IsOpponent (Side))
                yield return new Coordinate (x, At.Y + yDelta);

            if (At.Y == passantRank)
            {
                Square passing = Game.Board[x, At.Y];
                if (!target.IsOccupied && passing.IsOpponent (this.Side) && passing is Pawn)
                    if (Game.Plies.Count > 0)
                        if (Game.Plies.Top.From.Y == At.Y + yDelta + yDelta)
                            if (Game.Plies.Top.From.X == x)
                                yield return new Coordinate (x, At.Y + yDelta);
            }
        }


        internal override IEnumerable GetMoves ()
        {
            int yDelta, homeRank, passantRank;

            if (this.Side == Player.White)
            { yDelta = 1; homeRank = 1; passantRank = Game.Board.YSize - 4; }
            else
            { yDelta = -1; homeRank = Game.Board.YSize - 2; passantRank = 3; }

            int y2 = At.Y + yDelta;
            if (Game.Board[At.X, y2] == Square.Empty)
            {
                yield return new Coordinate (At.X, y2);

                if (At.Y == homeRank)
                {
                    y2 = y2 + yDelta;
                    if (Game.Board[At.X, y2] == Square.Empty)
                        yield return new Coordinate (At.X, y2);
                }
            }

            if (At.X > 0)
                foreach (Coordinate cap in GetCaptures (At.X - 1, yDelta, passantRank))
                    yield return cap;

            if (At.X < Game.Board.XSize - 1)
                foreach (Coordinate cap in GetCaptures (At.X + 1, yDelta, passantRank))
                    yield return cap;
        }
    }
}
